/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkFile, ts } from 'arkanalyzer/lib';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import { Defects, IssueReport } from '../../model/Defects';
import { MatcherCallback, MatcherTypes, AstMatcher } from '../../matcher/Matchers';
import { Rule } from '../../model/Rule';
import { RuleListUtil } from '../../utils/common/DefectsList';
import { RuleFix } from '../../model/Fix';

interface Issue {
    line: number;
    column: number;
    columnEnd: number;
    message: string;
    filePath: string,
    fixCode: string;
}

export class NoExtraSemiCheck implements BaseChecker {
    metaData: BaseMetaData = {
        severity: 2,
        ruleDocPath: 'docs/no-extra-semi.md',
        description: 'Disallow unnecessary semicolons.'
    };
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    
    private createMatcher = (predicate: (node: ts.Node) => boolean): AstMatcher => ({
        matcherType: MatcherTypes.AST,
        match: predicate
    });

    private readonly matchers = {
        emptyStatement: this.createMatcher(node => node.kind === ts.SyntaxKind.EmptyStatement),
        semicolonClassElement: this.createMatcher(node => node.kind === ts.SyntaxKind.SemicolonClassElement)
    };

    public registerMatchers(): MatcherCallback[] {
        return [
            { matcher: this.matchers.emptyStatement, callback: this.checkEmptyStatement },
            { matcher: this.matchers.semicolonClassElement, callback: this.checkSemicolonClassElement }
        ];
    }

    public static readonly allowedParentTypes: ts.SyntaxKind[] = [
        ts.SyntaxKind.ForStatement,
        ts.SyntaxKind.ForInStatement,
        ts.SyntaxKind.ForOfStatement,
        ts.SyntaxKind.WhileStatement,
        ts.SyntaxKind.DoStatement,
        ts.SyntaxKind.IfStatement,
        ts.SyntaxKind.LabeledStatement,
        ts.SyntaxKind.WithStatement,
    ];

    public check = (): void => {
    };

    public checkEmptyStatement = (node:ts.Node, target: ArkFile): void => {
        const parent = node.parent;
        if (!NoExtraSemiCheck.allowedParentTypes.includes(parent.kind)) {
            const sourceFile = node.getSourceFile();
            const start = node.getStart();
            const { line, character } = sourceFile.getLineAndCharacterOfPosition(start);
            // 保存修复内容到 Issue 对象
            const resultIssue: Issue = {
                line: line + 1,
                column: character + 1,
                columnEnd: character + 2,
                message: 'Unnecessary semicolon.',
                filePath: target.getFilePath() ?? '',
                fixCode: ''
            };
            const ruleFix = this.createFix(start, start + 1, resultIssue.fixCode);
            this.addIssueReport(resultIssue, ruleFix);
        }
    };

    public checkSemicolonClassElement = (node:ts.Node, target: ArkFile): void => {
        const lastChild = node.getChildAt(node.getChildCount() - 1);
        if (lastChild && lastChild?.kind === ts.SyntaxKind.SemicolonToken) {
            const sourceFile = lastChild.getSourceFile();
            const start = lastChild.getStart();
            const { line, character } = sourceFile.getLineAndCharacterOfPosition(start);
            const resultIssue: Issue = {
                line: line + 1,
                column: character + 1,
                columnEnd: character + 2,
                message: 'Unnecessary semicolon.',
                filePath: target.getFilePath() ?? '',
                fixCode: ''
            };
            const ruleFix = this.createFix(start, start + 1, resultIssue.fixCode);
            this.addIssueReport(resultIssue, ruleFix);
        }
    };

    private createFix(start: number, end: number, code: string): RuleFix {
        return { range: [start, end], text: code };
    };

    private addIssueReport(issue: Issue, ruleFix?: RuleFix): void {
        this.metaData.description = issue.message;
        const severity = this.rule.alert ?? this.metaData.severity;
        const defects = new Defects(issue.line, issue.column, issue.columnEnd, this.metaData.description, severity, this.rule.ruleId, issue.filePath,
            this.metaData.ruleDocPath, true, false, (ruleFix != undefined ? true : false));
        this.issues.push(new IssueReport(defects, ruleFix));
        RuleListUtil.push(defects);
    };
}